-- MonsterBuildingM
-- Created by cheny3 Jun/02/2016
-- 回合刷怪建筑

module("MonsterBuildingM", package.seeall);

-- 内部函数
local whenPlayerMove;
local whenLeaveDungeon;
local whenNextFloor;
local whenCombatRound;

-- 配置表
local monsterBuilding = {};

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    monsterBuilding = {};

    -- 配置表
    local monster_building_CSV = get_monster_building_CSV();
    for i = 1, #monster_building_CSV do
        monsterBuilding[monster_building_CSV[i].id] = monster_building_CSV[i];
    end
    monster_building_CSV = {};
end

-- 初始化
function init()
    if not _init then
        loadCsv();
    end

    -- 移除所有关注
    EventMgr.removeAll("MonsterBuildingM");

    -- 关注回合数变化
    CombatM.registerStartRoundCB("monster_building_start_round", whenCombatRound);
    EventMgr.register("MonsterBuildingM", event.START_ROUND, function(args)
        -- 纯粹为了模块加载
    end);

    -- 关注玩家离开地牢的事件
    EventMgr.register("MonsterBuildingM", event.LEAVE_DUNGEON, function()
        whenLeaveDungeon();
    end);

    -- 关注进入下一层的事件
    EventMgr.register("MonsterBuildingM", event.NEXT_FLOOR, function()
        whenNextFloor();
    end);

    -- 关注层数回退（大地之门等技能）的事件
    EventMgr.register("MonsterBuildingM", event.BACK_FLOOR, function()
        whenNextFloor();
    end);
end

-- 回合数变化
function whenCombatRound()
    allRefreshMonster();
end

-- 关注玩家离开地牢的事件
function whenLeaveDungeon()
    -- 清除刷怪建筑信息
    if ME.user.dbase:query("monster_building_info") then
        ME.user.dbase:delete("monster_building_info");
    end
end

-- 进入下一层的回调
function whenNextFloor()
    -- 清除刷怪建筑信息
    if ME.user.dbase:query("monster_building_info") then
        ME.user.dbase:delete("monster_building_info");
    end
end

-- 获取刷怪轮次倒计时
function getRefreshCountDown(classId, state)
    if  state == GRID_STATE_DISABLE then
        -- 已经失效了
        return nil;
    end

    local allInfo = ME.user.dbase:query("monster_building_info", {});
    local info = allInfo[classId] or {};
    local buildingInfo = query(classId);

    local curNum = info["monster_num"] or 0;
    local maxNum = buildingInfo["max_num"];
    if curNum >= maxNum then
        -- 刷怪数量已达上限
        return nil;
    end

    local round = buildingInfo["round"];
    local nowRound = CombatM.getRound() - 1;

    return round  - nowRound % round;
end

-- 检索
function query(classId, path)
    local m = table.deepcopy(monsterBuilding[classId]);

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 查询刷怪提示
function queryRefreshTip(class, pos)
    local newGrid = DungeonM.getGridByPos(pos);
    if not newGrid or not newGrid.monster then
        return nil;
    end

    local tip = MonsterBuildingM.query(class, "refresh_tip");
    if type(tip) ~= "string" or string.len(tip) == 0 then
        return nil;
    end

    local name = MonsterM.query(newGrid.monster.classId, "name");
    tip = string.gsub(tip, '{monster_name}', name);

    return tip;
end

-- 获取刷怪建筑格子
function findMonsterBuildingGrids()
    local ret = {};

    -- 先取出所有格子
    local grids = DungeonM.getCurrentDungeon() or {};
    for _, grid in pairs(grids) do
        if grid.state ~= GRID_STATE_DISABLE and
            grid.type == GRID_TYPE_MONSTER_BUILDING then
            table.insert(ret, grid);
        end
    end

    return ret;
end

-- 是否存在已失效的格子
function isInvalidGridExists()
    -- 先取出所有格子
    local grids = DungeonM.getCurrentDungeon() or {};

    local count = 0;
    for _, grid in pairs(grids) do
        repeat
            -- 不能是已失效的地板
            if grid.type == GRID_TYPE_ODD_FLOOR and
                grid.state == GRID_STATE_DISABLE then
                break;
            end

            -- 必须是否已开启的空格或已失效的格子
            if (grid.type == GRID_TYPE_EMPTY and grid.state == GRID_STATE_OPEN) or
                grid.state == GRID_STATE_DISABLE then
                -- 不能刷在钥匙或者有奖励的格子上
                if grid.has_key ~= 1 and grid.bonus == nil then
                    count = count + 1;
                end
            end
        until true
    end

    return count > 0;
end

-- 查找已失效的格子（包含空格）
-- 如果存在多个，随机返回一个，否则返回nil
function findInvalidGrid()
    -- 先取出所有格子
    local grids = DungeonM.getCurrentDungeon() or {};

    local gridList = {};
    for _, grid in pairs(grids) do
        repeat
            -- 不能是已失效的地板
            if grid.type == GRID_TYPE_ODD_FLOOR and
               grid.state == GRID_STATE_DISABLE then
                break;
            end

            -- 必须是已开启的空格或已失效的格子
            if grid.state == GRID_STATE_DISABLE or
                (grid.type == GRID_TYPE_EMPTY and grid.state == GRID_STATE_OPEN) then
                -- 不能刷在钥匙或者有奖励的格子上
                if grid.has_key ~= 1 and grid.bonus == nil then
                    table.insert(gridList, grid);
                end
            end
        until true
    end

    local gridCount = #gridList;
    if gridCount == 0 then
        return nil;
    end

    if gridCount == 1 then
        return gridList[1];
    end

    -- 存在多个，随机返回一个
    local randSeed = DungeonM.getRandSeed("monster_building_find_grid");
    local index = randSeed % gridCount + 1;

    -- 根据位置排序
    table.sort(gridList, function(grid1, grid2) return grid1:getPos() < grid2:getPos(); end);

    return gridList[index];
end

-- 判断单个建筑是否刷出怪物
function isRefreshMonster(grid)
    if not grid then
        -- 格子不存在
        return false;
    end

    if not grid:isOpened() then
        -- 格子未翻开
        return false;
    end

    -- 是否存在已失效的格子
    if not isInvalidGridExists() then
        -- 没有已失效的格子
        trace("MonsterBuildingM", "没有失效的格子，不刷新怪物。");
        return false;
    end

    -- 当前建筑信息
    local classId = grid.class;
    local buildingInfo = query(classId);

    -- 回合数
    local round = buildingInfo["round"];

    -- 判断回合数
    if CombatM.getRound() % round ~= 0 then
        return false;
    end

    -- 取玩家身上的建筑信息
    local allInfo = ME.user.dbase:query("monster_building_info", {});
    local info = allInfo[classId] or {};

    -- 判断已刷出的数量
    local curNum = info["monster_num"] or 0;
    local maxNum = buildingInfo["max_num"];
    if curNum >= maxNum then
        -- 数量已达上限
        trace("MonsterBuildingM", "%s怪物刷新数量已达上限。", buildingInfo["name"]);
        return false;
    end

    -- 玩家使用了时间停止
    local prop = PropM.combine(ME.user, "stop_action", 1);
    if PropM.apply(prop, 1) > 0 then
        -- 玩家使用了时间停止
        trace("MonsterBuildingM", "玩家使用了时间停止。");
        return false;
    end

    return true;
end

-- 尝试刷怪物
function allRefreshMonster()
    local invalidGrids = findMonsterBuildingGrids();
    if #invalidGrids <= 0 then
        -- 格子不存在
        return false;
    end

    for _, grid in pairs(invalidGrids) do
        refreshMonster(grid);
    end

    return true;
end

-- 单个建筑尝试刷出怪物
function refreshMonster(grid)
    if not isRefreshMonster(grid) then
        -- 不满足刷新条件，不处理
        return false;
    end

    -- 获取建筑信息
    local classId = grid.class;
    local buildingInfo = query(classId);

    trace("MonsterBuildingM", "%s回合数更新至：%d", buildingInfo["name"], CombatM.getRound());

    -- 随机选择一个已失效的格子
    local emptyGrid = findInvalidGrid();
    if not emptyGrid then
        return false;
    end

    -- 抽取怪物
    local element = fetchMonster(grid);
    if not element then
        trace("MonsterBuildingM", "抽取怪物失败了。");
        return false;
    end

    -- 将格子刷成怪
    local pos = emptyGrid:getPos();
    DungeonM.transGridToMonster(pos, element, "monster_building");

    -- 获取玩家身上的建筑信息
    local allInfo = ME.user.dbase:query("monster_building_info", {});
    local info = allInfo[classId] or {};

    -- 增加已刷出数量
    local curNum = (info["monster_num"] or 0) + 1;
    info["monster_num"] = curNum;
    allInfo[classId] = info;
    ME.user.dbase:set("monster_building_info", allInfo);

    trace("MonsterBuildingM", "格子%d刷出第%d个怪物(element = %d)",
        pos, curNum, element);

    -- 抛出刷怪事件
    EventMgr.fire(event.MONSTER_BUILDING_REFRESH, { ["element"] = element, ["class"] = classId, ["pos"] = pos, });

    local maxNum = buildingInfo["max_num"];
    if curNum >= maxNum and buildingInfo["auto_disappear"] == 1 then
        -- 已经到达刷怪上限了，格子置为失效
        grid:changeState(GRID_STATE_DISABLE);

        -- 抛出格子失效事件
        EventMgr.fire(event.MONSTER_BUILDING_DISABLE, {["pos"] = grid:getPos()});
    end

    return true;
end

-- 抽取一个怪物
function fetchMonster(grid)
    -- 格子类型
    local classId = grid.class;
    local buildingInfo = query(classId);

    -- 如果配置了抽取公式，直接使用公式抽取
    local formulaName = buildingInfo["fetch_formula"];
    if type(formulaName) == "string" and formulaName ~= "" then
        return FormulaM.invoke(formulaName, classId);
    end

    -- 当前迷宫
    local dungeonId = DungeonM.getDungeonId();
    local elements = DungeonM.getElementsByDungeon(dungeonId);
    if not elements then
        -- 该副本没有配置元素？
        return nil;
    end

    local class = DungeonM.getClassByType(GRID_TYPE_MONSTER, "monster");
    if not elements[class] then
        -- 该副本没有配置怪物？
        return nil;
    end

    local seed = DungeonM.getRandSeed("fetch_monster");

    -- 根据权重抽取一个怪物
    local ret = fetchElemBySeed(elements[class], seed);

    return ret["id"];
end
